<template>
  <div class="fs-virtual-waterfall-container">
    <div class="fs-virtual-waterfall-content" ref="contentRef">
        <div class="fs-virtual-waterfall-list" :style="contentStyle">
            <div class="fs-virtual-waterfall-item" v-for="{item,style} in renderList" :key="item.id" :style="style">
                <slot name="item" :item="item"></slot>
            </div>
        </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { reactive , type CSSProperties } from 'vue';
import type {IVirtualWaterFallProps, IDataItem,  IColumnQueue, IRenderItem, IItemRect} from './type'
import { List } from 'immutable';
import { computed } from 'vue';
import { markRaw } from 'vue';
import { ref } from 'vue';
import { onMounted } from 'vue';
import { debounce, rafThrottle } from "./tool";
import { onUnmounted } from 'vue';
const props=withDefaults(defineProps<IVirtualWaterFallProps>(),{
    request:async()=>({list:[],total:0}),
    gap:20,
    column:2,
    columnItemCount:3,
    requestSize:20

})

const emit=defineEmits<{
    finishLoadData:[]
}>()

defineSlots<{
    item(props:{item:IDataItem}):any
}>()

//数据状态
const dataState=reactive({
   loading:false,
   isFinish:false,
   currtentPage:1,
   total:0,
   list:[] as IDataItem[]

})



// 列状态
const columnState=reactive({
    queue:Array(props.column)
    .fill(0)
    .map<IColumnQueue>(()=>({list:[],height:0})),//一个数组，包含每列的队列信息。每列是一个 IColumnQueue 对象
    len:0,// 当前已处理的数据项数量
})
// 滚动状态
const scrollState=reactive({
    viewHeight:0,//可视区域的高度。
    viewWidth:0,//可视区域的宽度
    start:0,// 滚动起始位置
    get end(){
        return this.start+this.viewHeight
    }
})
const itemSizeInfo=computed(()=>dataState.list.reduce<Map<IDataItem["id"],IItemRect>>((pre,current)=>{
    const itemWidth=Math.floor((scrollState.viewWidth-(props.column-1)*props.gap)/props.column)
    pre.set(current.id,{
        width:itemWidth,
        height:Math.floor((itemWidth*current.height)/current.width)
    })
    return pre
},new Map()))
// 请求大小,每次请求的数据量
const requestSize=computed(()=>props.requestSize||props.column*props.columnItemCount)
//加载数据
const loadDataList=async()=>{
    if(dataState.isFinish)return
    dataState.loading=true
    const {list,total}=await props.request(dataState.currtentPage++,requestSize.value)
    if(!list.length){
        dataState.isFinish=true
        emit("finishLoadData")
        return
    }
    dataState.list.push(...markRaw(list))
    dataState.total=total
    dataState.loading=false
}

//计算最大、小高度及其索引
const computedHeight=computed(()=>{
    let minIndex=0,
    maxIndex=0,
    minHeight=Infinity,
    maxHeight=-Infinity
    columnState.queue.forEach(({height},index)=>{
       if(height<=minHeight){
        minHeight=height;
        minIndex=index
       }
       if(height>=maxHeight){
        maxHeight=height
        maxIndex=index
       }
    })
    return {
        minIndex,
        maxIndex,
        minHeight,
        maxHeight
    }

})
const contentStyle = computed(() => ({ height: `${computedHeight.value.maxHeight}px` } as CSSProperties));

//是否有更多数据
const hasMoreData=computed(()=>columnState.len<dataState.list.length)
//加入新的数据到容器中高度最低的那一列下面
const addInQueue=(size=Math.floor(props.column))=>{
    for(let i=0;i<size;i++){
        if(!hasMoreData.value)return;
        const dataItem=dataState.list[columnState.len]
        const minIndex=computedHeight.value.minIndex
        const currentQueue=columnState.queue[minIndex]
        const before=currentQueue.list[currentQueue.list.length-1]||null
        const genItem=generatorItem(dataItem,before,minIndex)
       currentQueue.list.push(genItem)
       currentQueue.height+=genItem.h
        columnState.len++

    }
}


//将容器里的几列数据全部整合到一起
const columnList=computed(()=>columnState.queue.reduce<IRenderItem[]>((pre,current)=>pre.concat(current.list),[]))

//在视口范围内的元素计算    
const renderList=computed(()=>columnList.value.filter(i=>i.h+i.y>scrollState.start&&i.y<scrollState.end))

//将原来照片数据转换为渲染到页面中的数据
const generatorItem=(item:IDataItem,before:IRenderItem|null,index:number):IRenderItem=>{
    const rect=itemSizeInfo.value.get(item.id)
    const width=rect?.width||0;
    const height=rect?.height||0;
    let y=0
    if(before)y=before.y+before.h+props.gap
    return markRaw({
         item,
         y,
         h:height,
         style:{
           width:`${width}px`,
           height:`${height}px`,
           transform:`translate3d(${index===0?0:(width+props.gap)*index}px,${y}px,0)` 
         }
    })
}

const contentRef=ref<HTMLDivElement>()

const handleScroll=rafThrottle(()=>{
     const {scrollTop,clientHeight}=contentRef.value!;
     scrollState.start=scrollTop
     if(!dataState.loading&&!hasMoreData.value){
        loadDataList().then(()=>{
            addInQueue()
        })
        return
     }
     if(scrollTop+clientHeight>computedHeight.value.minHeight){
        addInQueue()
     }

})

const handleResize=debounce(()=>{
    initSrollState()
    reComputedColumn()

},300)
const reComputedColumn=()=>{
    columnState.queue=columnState.queue.map((q,index)=>{
        let height=0;
        const list=q.list.reduce<IRenderItem[]>((pre,{item},i)=>{
          const before=pre[i-1]||null
          const result=generatorItem(item,before,index)
          height+=result.h;
          pre.push(result)
          return pre
        },[])
        return{
            height,
            list
        }
    })
}

//初始化容器
const initSrollState=()=>{
     scrollState.viewWidth=contentRef.value?.clientWidth||0;
     scrollState.viewHeight=contentRef.value?.clientHeight||0;
     scrollState.start=contentRef.value?.scrollTop||0
}
const init= async()=>{
    initSrollState()
    await loadDataList()
    addInQueue(requestSize.value)
    contentRef.value?.addEventListener("scroll",handleScroll)
    window.addEventListener("resize", handleResize)
}
const destroy=()=>{
    contentRef.value?.removeEventListener("scroll",handleScroll)
    window.removeEventListener("resize",handleResize)
}
onMounted(() => {
    init()
})

onUnmounted(() => {
    destroy()
})
</script>

<style scoped lang="scss">
.fs-virtual-waterfall{
    &-container{
        width: 100%;
        height: 100%;
    }
    &-content{
        width: 100%;
        height: 100%;
        overflow-y: scroll;

    }
    &-list{
        position: relative;
        width: 100%;
    }
    &-item{
        position: absolute;
        top: 0;
        left: 0;
        transition: all 0.3s;
        box-sizing: border-box;

    }
}
</style>